package exerr

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/gogo/status"
)

type content struct {
	HTTPStatus int         `json:"-"`                // http状态码
	Code       int32       `json:"code"`             // 自定义状态码
	Message    string      `json:"message"`          // 描述
	Submsg     string      `json:"submsg,omitempty"` // 详细描述
	Data       interface{} `json:"data,omitempty"`   // 响应数据
}

var exerrMap = make(map[int32]IExerr)

// Register 注册错误
func Register(httpStatus int, code int32, message, submsg string) IExerr {
	if _, exist := exerrMap[code]; exist {
		panic(fmt.Sprintf("errno %d already exists", code))
	}
	e := newErr(httpStatus, code, message, submsg, nil)
	exerrMap[code] = e
	return newErr(httpStatus, code, message, submsg, nil)
}

func newErr(status int, code int32, message, submsg string, data interface{}) *content {
	return &content{
		HTTPStatus: status,
		Code:       code,
		Message:    message,
		Submsg:     submsg,
		Data:       data,
	}
}

type IExerr interface {
	Error() string
	GetHTTPStatus() int
	GetCode() int32
	GetMessage() string
	GetSubmsg() string
	GetData() interface{}

	WithMessage(message string) IExerr
	WithSubmsg(msg string) IExerr
	WithData(data interface{}) IExerr
	ToString() string
}

func (ex *content) Error() string {
	return ex.ToString()
}

func (ex *content) GetHTTPStatus() int {
	return ex.HTTPStatus
}

func (ex *content) GetCode() int32 {
	return ex.Code
}

func (ex *content) GetMessage() string {
	return ex.Message
}

func (ex *content) GetSubmsg() string {
	return ex.Submsg
}

func (ex *content) GetData() interface{} {
	return ex.Data
}

// WithMessage 自定义响应信息
func (ex *content) WithMessage(message string) IExerr {
	return newErr(ex.GetHTTPStatus(), ex.Code, message, ex.Submsg, ex.Data)
}

// WithSubmsg 自定义响应提示
func (ex *content) WithSubmsg(prompt string) IExerr {
	ex.GetHTTPStatus()
	return newErr(ex.GetHTTPStatus(), ex.Code, ex.Message, prompt, ex.Data)
}

// WithData 追加响应数据
func (ex *content) WithData(data interface{}) IExerr {
	return newErr(ex.GetHTTPStatus(), ex.Code, ex.Message, ex.Submsg, data)
}

// ToString 返回 JSON 格式的错误详情
func (ex *content) ToString() string {
	err := &content{
		HTTPStatus: ex.HTTPStatus,
		Code:       ex.Code,
		Message:    ex.Message,
		Submsg:     ex.Submsg,
		Data:       ex.Data,
	}
	raw, _ := json.Marshal(err)
	return string(raw)
}

// Parse 解析错误
func Parse(err interface{}) IExerr {
	if err == nil {
		return nil
	}
	switch e := err.(type) {
	case IExerr:
		return e
	case error:
		// 获取gRPC的错误code
		if s, ok := status.FromError(e); ok {
			en := GetErrNo(int32(s.Code()))
			newEn := clone(en)
			return newEn.(IExerr)
		}
		return Unknown.WithMessage(e.Error())
	default:
		return Unknown
	}
}

// GetErrNo 根据错误code获取错误
func GetErrNo(code int32) IExerr {
	if en, exist := exerrMap[code]; exist {
		return en
	}
	return Unknown
}

// 浅克隆
func clone(src interface{}) interface{} {
	typ := reflect.TypeOf(src)
	if typ.Kind() == reflect.Ptr { // 如果是指针类型
		typ = typ.Elem()               // 获取源实际类型(否则为指针类型)
		dst := reflect.New(typ).Elem() // 创建对象
		data := reflect.ValueOf(src)   // 源数据值
		data = data.Elem()             // 源数据实际值（否则为指针）
		dst.Set(data)                  // 设置数据
		dst = dst.Addr()               // 创建对象的地址（否则返回值）
		return dst.Interface()         // 返回地址
	} else {
		dst := reflect.New(typ).Elem() // 创建对象
		data := reflect.ValueOf(src)   // 源数据值
		dst.Set(data)                  // 设置数据
		return dst.Interface()         // 返回
	}
}
